﻿using Bentley.CifNET.GeometryModel;
using Bentley.CifNET.GeometryModel.SDK;
using Bentley.CifNET.LinearGeometry;
using Bentley.CifNET.SDK.Edit;
using CheccCulvertHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Checc.Culvert.LayingLine;

public class LayingLineHelper
{
    // Fields 浮点型的误差
    private const double DOUBLE_DELTA = 1E-06;

    private readonly double _angleAccuracy;
    private readonly double _culvertHeight;
    private readonly double _depthFill;
    private readonly LineElement _layingLine;
    private readonly Alignment _primaryAlignment;
    private readonly double _slope;

    public LayingLineHelper()
    {
    }

    public LayingLineHelper(Alignment primaryAlignment, LineElement layingLine, double culvertHeight, double depthFill, double slope, double angleAccuracy)
    {
        this._primaryAlignment = primaryAlignment;
        this._layingLine = layingLine;
        this._culvertHeight = culvertHeight;
        this._depthFill = depthFill;
        this._slope = slope;
        this._angleAccuracy = angleAccuracy;
    }

    /// <summary>
    ///绘制涵洞线
    /// </summary>
    /// <returns></returns>
    public (Alignment alignment, double bottomElevation, double angle, double disAlong) GetCulvertLayingLine()
    {
        var line = this.CreateLineElement();
        line.GetCurveVector().GetStartEnd(out var startPoint, out var endPoint, out _, out _);
        var pointOnAligment = this.GetIntersectionPoint();
        var elevation = this._primaryAlignment.ActiveProfile.ProfileGeometry.GetYAtX(pointOnAligment.DistanceAlong);

        //计算涵底高程
        var bottomElevation = elevation - this._depthFill - this._culvertHeight;
        // 交点桩号
        var disAlong = pointOnAligment.DistanceAlong;
        // 交角角度
        line.GetCurveVector().GetStartEnd(out _, out _, out var startTangentVector, out _);
        var angle = Math.Acos(startTangentVector.DotProductXY(pointOnAligment.TangentVector)) * 180 / Math.PI;

        ConsensusConnectionEdit sdkCon = ConsensusConnectionEdit.GetActive();
        Alignment alignment = Alignment.CreateFromElement(sdkCon, line);
        double distance = (startPoint * (1 / GeneralHelpers.UorToMeters)).Distance(pointOnAligment.Coordinates);
        double distance2 = (endPoint * (1 / GeneralHelpers.UorToMeters)).Distance(pointOnAligment.Coordinates);
        var startElevation = elevation - this._depthFill - distance * this._slope * 0.01;
        var endElevation = elevation - this._depthFill + distance2 * this._slope * 0.01;

        alignment.AddStationing(0.0, 0.0, StationingMethod.ByRegion);
        alignment.AddStationing(line.GetCurveVector().SumOfLengths(), line.GetCurveVector().SumOfLengths(), StationingMethod.ByRegion);
        var ss = alignment.Stationing;
        List<DPoint3d> points = new();

        var startPointProfile = new DPoint3d(0, startElevation, 0);
        var endPointProfile = new DPoint3d(alignment.BaseLinearGeometry.Length, endElevation, 0);
        points.Add(startPointProfile);
        points.Add(endPointProfile);
        ProfileLine profileLine = new(startPointProfile, endPointProfile);
        //ProfileComplex profileComplex = new ProfileComplex(new ProfileElement[] { profileLine });
        var profile = alignment.CreateProfileByProfileElement(profileLine, true, false);
        alignment.SetFeatureDefinition(@"Alignment\Road\Geom_Baseline");

        return (alignment, bottomElevation, angle, disAlong);
    }

    public DgnModel GetDefaultModel()
    {
        ModelId modelId = Session.Instance.GetActiveDgnFile().FindModelIdByName("Default");
        //根据起终点桩号、公路等级、长度 找到相对应的路线
        return Session.Instance.GetActiveDgnFile().FindLoadedModelById(modelId);
    }

    //相等
    private bool AreEqual(double value1, double value2)
    {
        return (value1 == value2)
            || System.Math.Abs(value1 - value2) < DOUBLE_DELTA;
    }

    /// <summary>
    /// 重绘涵洞线段
    /// </summary>
    /// <returns></returns>
    private LineElement CreateLineElement()
    {
        var pointOnPrimayLine = this.GetIntersectionPoint();
        this._layingLine.GetCurveVector().GetStartEnd(out var startPoint, out var endPoint, out var startTangentVector, out _);
        if (IsLeftOfLayingLineStartPoint())
        {
            var intersectAngle = Angle.Acos(pointOnPrimayLine.TangentVector.DotProduct(startTangentVector));
            var angle = this.GetAngleByStep(intersectAngle.Degrees, this._angleAccuracy);
            var vector = pointOnPrimayLine.TangentVector.RotateXY(new Angle() { Degrees = -angle });
            vector.TryNormalize(out var normalVector);
            var distancOfStartPointToIntersectPoint = startPoint.Distance(pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters);
            var distanceOfEndPointToIntersectPint = endPoint.Distance(pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters);
            var element = new LineElement(GetDefaultModel(), null,
                new DSegment3d(pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters - normalVector * distancOfStartPointToIntersectPoint,
                pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters + normalVector * distanceOfEndPointToIntersectPint));
            element.AddToModel();
            return element;
        }
        else
        {
            var intersectAngle = Angle.Acos(pointOnPrimayLine.TangentVector.DotProduct(-startTangentVector));
            var angle = this.GetAngleByStep(intersectAngle.Degrees, this._angleAccuracy);
            var vector = pointOnPrimayLine.TangentVector.RotateXY(new Angle() { Degrees = -angle });
            vector.TryNormalize(out var normalVector);
            var distancOfStartPointToIntersectPoint = startPoint.Distance(pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters);
            var distanceOfEndPointToIntersectPint = endPoint.Distance(pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters);
            var element = new LineElement(GetDefaultModel(), null,
               new DSegment3d(pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters - normalVector * distanceOfEndPointToIntersectPint,
               pointOnPrimayLine.Coordinates * GeneralHelpers.UorToMeters + normalVector * distancOfStartPointToIntersectPoint));
            element.AddToModel();
            return element;
        }
    }

    private double GetAngleByStep(double angle, double angleStep)
    {
        //最小
        double MinAngle = (int)(angle / angleStep) * angleStep;
        //最大
        double MaxAngle = (int)((angle / angleStep) + 1) * angleStep;
        //中间
        double MidAngle = (int)(angle / angleStep) * angleStep + (angleStep * 0.5);

        if (GreaterThanOrEqual(angle, MidAngle))
        {
            return MaxAngle;
        }
        else
        {
            return MinAngle;
        }
    }

    /// <summary>
    /// 获取路线交点
    /// </summary>
    /// <returns></returns>
    private LinearPoint GetIntersectionPoint()
    {
        this._layingLine.GetCurveVector().GetStartEnd(out var startPoint, out _, out var startTangent, out _);
        var points = this._primaryAlignment.BaseLinearGeometry.Intersect(startPoint * (1 / GeneralHelpers.UorToMeters), startTangent, 0.001);
        if (points != null && points.Count != 0)
        {
            List<double> doubles = new List<double>();
            for (int i = 0; i < points.Count; i++)
            {
                doubles.Add(startPoint.DistanceXY(points.Item(i).PointOn1.Coordinates * GeneralHelpers.UorToMeters));
            }
            return points.Item(doubles.IndexOf(doubles.Min())).PointOn1;
        }
        return null;
    }

    //大于或等于
    private bool GreaterThanOrEqual(double value1, double value2)
    {
        return (value1 > value2) || AreEqual(value1, value2);
    }

    /// <summary>
    /// 判断起点是否是再路线的左侧
    /// </summary>
    /// <returns></returns>
    private bool IsLeftOfLayingLineStartPoint()
    {
        this._layingLine.GetCurveVector().GetStartEnd(out var startPoint, out _, out var startTangent, out _);
        var pointOnAlignment = this.GetIntersectionPoint();
        var vector = pointOnAlignment.TangentVector.CrossProduct(startTangent);
        return vector.Z < 0;
    }
}